iOS多线程 --- pthread和NSThread
Comment多线程,是实现多个线程并发执行的技术,对于单核CPU来说,虽然在硬件的限制下同时只能执行一个线程,但是操作系统可以快速的在不同线程之间切换,在很小的切换时间下,来给用户造成一种同时运行多个线程的假象,而对于多核CPU,因为有硬件的支持而能够在同一时间同时运行多个线程
iOS中有以下几种线程方案
- Pthread
- NSThread
- GCD
- NSOperationQueue
Pthread
Pthread是一套通用的多线程方案,可以在类Unix操作系统(如Unix,Linux,Mac OS)等系统使用,它使用C语言编写,需要程序员自己管理线程的生命周期,使用难度较大,在开发中几乎不会直接使用它。
使用pthread创建一个线程
1 | //声明一个thread变量 |
打印结果:1
22019-12-04 21:37:25.502129+0800 Thread[2601:163643] Current Thread2:<NSThread: 0x600002aa5040>{number = 6, name = (null)}
2019-12-04 21:37:25.503243+0800 Thread[2601:163558] Current Thread1:<NSThread: 0x600002ad5040>{number = 1, name = main}
可以看到在pthread中通过pthread_create函数来创建线程,pthread_create(&thread, NULL, test, NULL)有四个参数,各项参数含义如下:
第一个参数:指向线程(标识符)的指针
第二个参数:用来设置线程属性 ,通常设置为NULL
第三个参数:线程运行的函数地址
第四个参数:运行函数的参数
其中第一、二个参数比较简单,这里再额外介绍一下第三四个参数,
第三个参数”线程属性“是一个pthread_attr_t类型的结构体,这个结构体如下:1
2
3
4
5
6
7
8
9
10
11
12typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void *stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
要使用pthread_attr_t我们需要对它进行初始化,使用后还要去初始化
pthread_attr_t初始化函数:1
pthread_attr_init()
pthread_attr_t去初始化函数:1
pthread_attr_destory()
pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值
如果想修改这些默认值,pthread_attr_t的每一个属性都有相应的函数对其进行查看和修改
比如修改分离状态可以用:1
2int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);
设置的时候可以有两种选择:
<1>.detachstate参数为:PTHREAD_CREATE_DETACHED 分离状态启动
<2>.detachstate参数为:PTHREAD_CREATE_JOINABLE 正常启动线程2>1>
线程的继承性:1
2int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
参数支持:
PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数
PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于 schedpolicy和schedparam属性中显式 设置的调度信息
pthread_attr_t的其他属性都有相应的函数来修改,这里就不一一介绍了,感兴趣的同学可以在stackoverflow的when pthread_attr_t is not NULL?问题中了解到更多信息
第四个参数:
运行函数传递的参数,当传递单个参数时,可以直接定义一个变量传递给线程函数,当需要传递多个参数时候,就需要创建一个结构体来包含所有的参数,然后再传入线程函数:
传单个参数的情况
1 | //定义一个函数 |
传多个参数:
1 | //定义一个结构体 |
除了pthread_create,Pthread 还有以下相关函数:
- pthread_exit() 终止当前线程
- pthread_cancel() 中断另外一个线程的运行
- pthread_join() 阻塞当前的线程,直到另外一个线程运行结束
- pthread_attr_init() 初始化线程的属性
- pthread_attr_setdetachstate() 设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
- pthread_attr_getdetachstate() 获取脱离状态的属性
- pthread_attr_destroy() 删除线程的属性
- pthread_kill() 向线程发送一个信号
我们通常并不会直接使用pthread来管理线程,所以对pthread不再做过多的介绍
NSThread
NSThread和pthread都是对内核 mach kernal的mach thread的封装,不同的是NSThread是由苹果官方提供的,可以直接操作线程对象,使用起来比pthread更加易用,不过仍需要自己管理生命周期、同步、加锁的问题
创建线程:1
2
3
4
5
6
7//先创建线程,再启动线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sailTickets) object:nil];
[thread start];
//先创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
//隐式创建并启动线程
[self performSelectorInBackground:@selector(test) withObject:nil];
线程相关方法1
2
3
4
5
6
7
8
9
10
11
12// 获得主线程
+ (NSThread *)mainThread;
// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;
// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;
// 获得当前线程
NSThread *current = [NSThread currentThread];
// 线程的名字——setter方法
- (void)setName:(NSString *)n;
// 线程的名字——getter方法
- (NSString *)name;
线程状态控制方法1
2
3
4- (void)start;// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
+ (void)sleepUntilDate:(NSDate *)date;// 线程进入阻塞状态
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// 线程进入阻塞状态
+ (void)exit;// 线程进入死亡状态
线程之间的通信
在开发中,我们经常会在子线程进行耗时操作,操作结束后再回到主线程去刷新 UI。这就涉及到了子线程和主线程之间的通信。我们先来了解一下官方关于 NSThread 的线程间通信的方法
1 | // 在主线程上执行操作 |
线程的五个状态
New Runnabled Running Blocked Dead
New:1
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
Runnabled:1
[thread start];
Running
CPU负责调度可调度线程池中的处于就绪状态的线程,线程在执行结束之前,状态可能在就绪和运行之间来回的切换,就绪和运行之间的状态切换由CPU来完成,我们无法干涉
阻塞
正在运行的线程,当满足某个条件时,可以用休眠或者锁来阻塞线程的执行1
2
3
4
5
6//sleepForTimeInterval:休眠指定时长
[NSThread sleepForTimeInterval:1.0];
//sleepUntilDate:休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
//互斥锁
@synchronized(self)
Dead
线程正常死亡:线程执行结束
线程非正常死亡:线程突然崩溃/当满足某个条件后,在线程内部强制退出,调用exit方法
关于exit需要注意的地方:
不能在主线程中调用该方法.会使主线程退出.
当当前线程死亡之后,这个线程中的代码都不会被执行.
在调用此方法之前一定要注意释放之前由C语言框架创建的对象
线程状态的转换
当我们创建了一个线程,执行start方法后,系统把线程放入可调度线程池中,此时线程进入就绪(Runnabled)状态
如果CPU调度当前线程,则当前线程进入运行状态,当CPU调度其他线程,则当前线程回到就绪状态
如果系统在运行当前线程时候调用了sleep方法/等待同步锁,则当前线程进入阻塞状态,等到sleep到时/得到同步锁,则回到就绪状态
如果系统在运行当前线程对象的时候线程任务执行完毕/异常强制退出,则当前线程对象进入死亡状态
线程常驻
在一些场景中需要用到常驻线程,要使线程常驻就需要用到RunLoop,每个线程都有一个RunLoop这是默认没有开启,RunLoop的开启就意味着线程的常驻,比如主线程
下边这段代码来自AFNetworking1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
这里的_networkRequestThread就属于常驻线程,可以看到要想创建一个常驻线程,只要以下几步:
1.创建一个全局的thread变量1
@property (nonatomic, strong) NSThread *networkThread;
2.初始化线程并启动1
2self.networkThread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
[self.networkThread start];
3.在线程中开启RunLoop,子线程中的RunLoop是默认关闭的1
2
3
4
5- (void)run{
NSLog(@"%@", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
现在networkThread已经实现了常驻,当需要使用它的时候可以这么调用
4.利用常驻线程处理任务1
[self performSelector:@selector(action) onThread:self.networkThread withObject:nil waitUntilDone:NO ];